Non manhattan routing
Contents
Non manhattan routing#
gdsfactory provides functions to connect and route components ports that are off-grid or have non manhattan orientations (0, 90, 180, 270 degrees)
Fix Non manhattan connections#
The GDS format often has issues with non-manhattan shapes, due to the rounding of vertices to a unit grid and to downstream tools (i.e. DRC) which often tend to assume cell references only have rotations at 90 degree intervals. For example:
[1]:
from gdsfactory.decorators import has_valid_transformations
import gdsfactory as gf
from gdsfactory.generic_tech import get_generic_pdk
gf.config.rich_output()
PDK = get_generic_pdk()
PDK.activate()
@gf.cell
def demo_non_manhattan():
c = gf.Component("bend")
b = c << gf.components.bend_circular(angle=30)
s = c << gf.components.straight(length=5)
s.connect("o1", b.ports["o2"])
return c
c1 = demo_non_manhattan()
print(has_valid_transformations(c1))
c1
2023-02-20 17:47:17.232 | INFO | gdsfactory.config:<module>:50 - Load '/home/runner/work/gdsfactory/gdsfactory/gdsfactory' 6.43.1
2023-02-20 17:47:18.130 | INFO | gdsfactory.technology.layer_views:__init__:785 - Importing LayerViews from YAML file: /home/runner/work/gdsfactory/gdsfactory/gdsfactory/generic_tech/layer_views.yaml.
2023-02-20 17:47:18.137 | INFO | gdsfactory.pdk:activate:206 - 'generic' PDK is now active
False
demo_non_manhattan: uid 0e418af7, ports [], references ['bend_circular_1', 'straight_1'], 0 polygons
if you zoom in between the bends you will see a notch between waveguides due to non-manhattan connection between the bends.

You an fix it with the flatten_invalid_refs flag when you call Component.write_gds().
[2]:
help(c1.write_gds)
Help on method write_gds in module gdsfactory.component:
write_gds(gdspath: 'Optional[PathType]' = None, gdsdir: 'Optional[PathType]' = None, unit: 'Optional[float]' = None, precision: 'Optional[float]' = None, logging: 'bool' = True, on_duplicate_cell: 'Optional[str]' = None, flatten_invalid_refs: 'Optional[bool]' = None, max_points: 'Optional[int]' = None) -> 'Path' method of gdsfactory.component.Component instance
Write component to GDS and returns gdspath.
Args:
gdspath: GDS file path to write to.
gdsdir: directory for the GDS file. Defaults to /tmp/randomFile/gdsfactory.
unit: unit size for objects in library. 1um by default.
precision: for dimensions in the library (m). 1nm by default.
logging: disable GDS path logging, for example for showing it in KLayout.
on_duplicate_cell: specify how to resolve duplicate-named cells. Choose one of the following:
"warn" (default): overwrite all duplicate cells with one of the duplicates (arbitrarily).
"error": throw a ValueError when attempting to write a gds with duplicate cells.
"overwrite": overwrite all duplicate cells with one of the duplicates, without warning.
flatten_invalid_refs: flattens component references which have invalid transformations.
max_points: Maximal number of vertices per polygon.
Polygons with more vertices that this are automatically fractured.
[3]:
gdspath = c1.write_gds(flatten_invalid_refs=True)
c2 = gf.import_gds(gdspath)
c2
2023-02-20 17:47:18.985 | INFO | gdsfactory.component:_write_library:1704 - Wrote to '/tmp/tmpzn21wd6b/gdsfactory/demo_non_manhattan.gds'
demo_non_manhattan: uid 0d7c55bc, ports [], references ['bend_circular_angle30_1', 'straight_length5_transf_771f87f5_1'], 0 polygons
[4]:
has_valid_transformations(c1) # has gap issues
False
[5]:
has_valid_transformations(c2) # works perfect
True
If you zoom in the connection the decorator you can see it fixed the issue in c that we fixed in c2 thanks to the flatten_invalid_refs flag.

Default PDK GdsWriteSettings#
If you are frequently (or even sometimes!) creating geometries like this, with non-manhattan ports and/or references with non-90-degree rotations, I highly recommend that you set flatten_invalid_refs=True in your PDK’s GdsWriteSettings. If you are the PDK author, you can do this in the definition of the pdk. Or, you can modify the PDK at runtime like.
[6]:
pdk = gf.get_active_pdk()
pdk.gds_write_settings.flatten_invalid_refs = True
With this flag set, invalid references will be flattened by default, preventing gaps and errors in downstream tools which may not support cell references with arbitrary rotation, without needing to specify this on each GDS write.
You should note, however, that this will not fix all gaps between faces of unequal length, as it is impossible to guarantee this for diagonal line segments of unequal lengths constrained to end on integer grid values.
Non-manhattan router#
Warning: It is highly advised that you follow the above instructions and set your PDK to flatten invalid refs on GDS write by default if you intend to use the non-manhattan router.
The non-manhattan (all-angle) router allows you to route between ports and in directions which are not aligned with the x and y axes, which is the constraint of most other gdsfactory routers. Unlike phidl’s smooth() however, the all-angle router
has a
stepsbased syntax, fully compatible with the yaml-based circuit flowbuilds paths from available PDK components, such that routes can be simulated naturally by S-matrix-based circuit modeling tools, like SAX
allows for advanced logic in selecting appropriate bends, cross-sections, and automatic tapers, based on context
includes advanced cross-section-aware bundling logic
A simple route#
Let’s start with a simple route between two non-orthogonal ports. Consider the yaml-based pic below.
[7]:
from gdsfactory.read import cell_from_yaml_template
from IPython.display import Code
from pathlib import Path
from IPython.display import display
def show_yaml_pic(filepath):
gf.clear_cache()
cell_name = filepath.stem
return display(Code(filename=filepath, language='yaml+jinja'), cell_from_yaml_template(filepath, name=cell_name)())
# we're going to use yaml-based PICs for our examples. you can find them in docs/notebooks/yaml_pics
# if you'd like to tweak and play along
sample_dir = Path('yaml_pics')
basic_sample_fn = sample_dir / 'aar_simple.pic.yml'
show_yaml_pic(basic_sample_fn)
2023-02-20 17:47:19.737 | INFO | gdsfactory.technology.layer_views:__init__:785 - Importing LayerViews from YAML file: /home/runner/work/gdsfactory/gdsfactory/gdsfactory/generic_tech/layer_views.yaml.
instances:
mmi_long:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 10
mmi_short:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 5
placements:
mmi_short:
rotation: 180
mmi_long:
port: o1
rotation: 70 # Look Ma, I'm non-90!
x: mmi_short,o1
y: mmi_short,o1
dx: 20
dy: 20
routes:
optical:
routing_strategy: get_bundle_all_angle # the non-manhattan router
links:
mmi_short,o1: mmi_long,o1
ports:
o1: mmi_short,o2
o2: mmi_short,o3
aar_simple.pic: uid 7442fc20, ports ['o1', 'o2'], references ['mmi_long', 'mmi_short', 'extrude_1', 'bend_euler_1', 'extrude_2'], 0 polygons
You can see that even though one of the ports was non-orthogonal, the route was completed, using non-90-degree bends. The logic of how this works is explained further in the next section
Bends and connectors#
Let’s first consider the “simple” case, as shown above, where the vectors of the two ports to route between intersect at a point. The logic for how to build the route is as follows:
Find the intersection point of the two port vectors.
Place the bend at the intersection point of the two vectors by its “handle”. The bend’s handle is the point of intersetion of it’s inverted port vectors (i.e. if the ports were pointed inwards rather than outwards). For any arbitrary bend, this guarantees that the ports of the bend will be in the straight line of sight of the ports which they should connect to, inset by some amount.
Call the route or segment’s specified connector function to generate a straight section between the bend’s ports and their adjacent ports.
Now, this is where it gets fun. Since we’ve calculated our bend first and worked backwards, we know how much room we have for the straight connector, and we can take that into consideration when creating it.
The three connectors available by default are
low_loss: auto-tapers to the lowest-loss cross-section possible to fit in the given segmentauto_taper: auto-tapers to the cross-section specified, based on the active pdk’s specifiedlayer_transitionssimple: simply routes with a straight in the cross-section specified (no auto-tapering)
You can also define your own connector, as a function of the two ports which should be connected and the (suggested) cross-section. See the example below, which implements a very custom connector, composed of two sine bends and a physical label.
[8]:
import gdsfactory.routing.all_angle as aar
import numpy as np
def wonky_connector(port1, port2, cross_section):
# let's make a wavy-looking connector of two sine tapers, each half the length of the total connector
# we'll let cross_section define the cross-section at the *center* of the connector here
connector_length = np.linalg.norm(port2.center - port1.center)
t1 = gf.components.taper_cross_section_sine(length=0.5 * connector_length,
cross_section1=port1.cross_section,
cross_section2=cross_section).ref().connect('o1', port1)
t1.info['length'] = connector_length * 0.5
t2 = gf.components.taper_cross_section_sine(length=0.5 * connector_length,
cross_section1=port2.cross_section,
cross_section2=cross_section).ref().connect('o1', port2)
t2.info['length'] = connector_length * 0.5
center_port = t1.ports['o2']
# just for fun-- we can add a non-functional reference also
label = gf.components.text(f'W = {center_port.width}, L = {connector_length:.3f}', size=center_port.width * 0.5, justify="center", layer="M1").ref()
label.move(label.center, destination=center_port.center + (0, center_port.width)).rotate(center_port.orientation, center=center_port.center)
label.info['length'] = 0
return [t1, t2, label]
# register the connector so it can be used by name
aar.CONNECTORS['wonky'] = wonky_connector
wonky_fn = sample_dir / 'aar_wonky_connector.pic.yml'
show_yaml_pic(wonky_fn)
2023-02-20 17:47:20.604 | INFO | gdsfactory.technology.layer_views:__init__:785 - Importing LayerViews from YAML file: /home/runner/work/gdsfactory/gdsfactory/gdsfactory/generic_tech/layer_views.yaml.
instances:
mmi_long:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 10
mmi_short:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 5
placements:
mmi_short:
rotation: 180
mmi_long:
port: o1
rotation: 70
x: mmi_short,o1
y: mmi_short,o1
dx: 20
dy: 20
routes:
optical:
routing_strategy: get_bundle_all_angle
settings:
end_connector: wonky # using our new, wonky connector for the final segment
end_cross_section: # and for that final segment, also tip the connector to use this cross-section
cross_section: strip
settings:
width: 2.0
links:
mmi_short,o1: mmi_long,o1
ports:
o1: mmi_short,o2
o2: mmi_short,o3
aar_wonky_connector.pic: uid 33ccac98, ports ['o1', 'o2'], references ['mmi_long', 'mmi_short', 'extrude_1', 'bend_euler_1', 'taper_cross_section_1', 'taper_cross_section_2', 'text_1'], 0 polygons
Indirect routes#
Indirect routes are those in which the port vectors do not intersect. In this case, you will see that an S-like bend is created.
[9]:
indirect_fn = sample_dir / "aar_indirect.pic.yml"
show_yaml_pic(indirect_fn)
2023-02-20 17:47:21.367 | INFO | gdsfactory.technology.layer_views:__init__:785 - Importing LayerViews from YAML file: /home/runner/work/gdsfactory/gdsfactory/gdsfactory/generic_tech/layer_views.yaml.
instances:
mmi_long:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 10
mmi_short:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 5
placements:
mmi_short:
rotation: 180
mmi_long:
port: o1
rotation: -10 # port vectors no longer intersect
x: mmi_short,o1
y: mmi_short,o1
dx: 50
dy: 20
routes:
optical:
routing_strategy: get_bundle_all_angle
links:
mmi_short,o1: mmi_long,o1
ports:
o1: mmi_short,o2
o2: mmi_short,o3
aar_indirect.pic: uid f787f653, ports ['o1', 'o2'], references ['mmi_long', 'mmi_short', 'bend_euler_1', 'bend_euler_2', 'taper_1', 'extrude_1', 'taper_2'], 0 polygons
This is also capable of looping around, i.e. for ~180 degree connections.
[10]:
show_yaml_pic(sample_dir / 'aar_around_the_back.pic.yml')
2023-02-20 17:47:22.420 | INFO | gdsfactory.technology.layer_views:__init__:785 - Importing LayerViews from YAML file: /home/runner/work/gdsfactory/gdsfactory/gdsfactory/generic_tech/layer_views.yaml.
instances:
mmi_long:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 10
mmi_short:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 5
placements:
mmi_short:
rotation: 180
mmi_long:
port: o1
rotation: -140 # ~180 degree connection
x: mmi_short,o1
y: mmi_short,o1
dx: 50
dy: 20
routes:
optical:
routing_strategy: get_bundle_all_angle
links:
mmi_short,o1: mmi_long,o1
ports:
o1: mmi_short,o2
o2: mmi_short,o3
aar_around_the_back.pic: uid 8bd45163, ports ['o1', 'o2'], references ['mmi_long', 'mmi_short', 'bend_euler_1', 'bend_euler_2', 'taper_1', 'extrude_1', 'taper_2'], 0 polygons
We can fine-tune how this looks by adjusting the start_angle and end_angle of the route, which will abut a bend to the start/end ports such that they exit at the angle specified.
[11]:
show_yaml_pic(sample_dir / 'aar_around_the_back2.pic.yml')
2023-02-20 17:47:23.297 | INFO | gdsfactory.technology.layer_views:__init__:785 - Importing LayerViews from YAML file: /home/runner/work/gdsfactory/gdsfactory/gdsfactory/generic_tech/layer_views.yaml.
instances:
mmi_long:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 10
mmi_short:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 5
placements:
mmi_short:
rotation: 180
mmi_long:
port: o1
rotation: -140
x: mmi_short,o1
y: mmi_short,o1
dx: 50
dy: 20
routes:
optical:
routing_strategy: get_bundle_all_angle
settings:
start_angle: 90 # exit out of the starting port at this angle
end_angle: 180 # exit out of the ending port at this angle
links:
mmi_short,o1: mmi_long,o1
ports:
o1: mmi_short,o2
o2: mmi_short,o3
aar_around_the_back2.pic: uid ac12a6a9, ports ['o1', 'o2'], references ['mmi_long', 'mmi_short', 'bend_euler_1', 'bend_euler_2', 'extrude_1', 'bend_euler_3', 'taper_1', 'extrude_2', 'taper_2'], 0 polygons
You may also want to further customize the bend used in the route, as shown below.
[12]:
show_yaml_pic(sample_dir / 'aar_around_the_back3.pic.yml')
2023-02-20 17:47:24.068 | INFO | gdsfactory.technology.layer_views:__init__:785 - Importing LayerViews from YAML file: /home/runner/work/gdsfactory/gdsfactory/gdsfactory/generic_tech/layer_views.yaml.
instances:
mmi_long:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 10
mmi_short:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 5
placements:
mmi_short:
rotation: 180
mmi_long:
port: o1
rotation: -140
x: mmi_short,o1
y: mmi_short,o1
dx: 50
dy: 20
routes:
optical:
routing_strategy: get_bundle_all_angle
settings:
bend: # you can specify a custom bend to use, either by name or with expanded syntax as shown
component: bend_circular
settings:
radius: 5
start_angle: 90
end_angle: 180
links:
mmi_short,o1: mmi_long,o1
ports:
o1: mmi_short,o2
o2: mmi_short,o3
aar_around_the_back3.pic: uid c7e6d78d, ports ['o1', 'o2'], references ['mmi_long', 'mmi_short', 'bend_circular_1', 'bend_circular_2', 'extrude_1', 'bend_circular_3', 'taper_1', 'extrude_2', 'taper_2'], 0 polygons
Steps#
For more complex routes, i.e. when weaving around obstacles, you may want to fine-tune the path that the route traverses. We can do this by defining steps.
[13]:
show_yaml_pic(sample_dir / 'aar_steps01.pic.yml')
2023-02-20 17:47:24.914 | INFO | gdsfactory.technology.layer_views:__init__:785 - Importing LayerViews from YAML file: /home/runner/work/gdsfactory/gdsfactory/gdsfactory/generic_tech/layer_views.yaml.
instances:
mmi_long:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 10
mmi_short:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 5
obstacle1:
component: rectangle
settings:
layer: M1
size: [30, 30]
placements:
mmi_short:
rotation: 180
mmi_long:
port: o1
rotation: 20
x: mmi_short,o1
y: mmi_short,o1
dx: 120
dy: 80
obstacle1:
port: ce
x: mmi_long,o1
y: mmi_long,o1
dx: -20
dy: -10
routes:
optical:
routing_strategy: get_bundle_all_angle
settings:
steps:
- ds: 50 # proceed out of the start port by 50 um
exit_angle: 90 # then exit at 90 deg
- ds: 100 # go another 100 um
exit_angle: 0 # exit at 0 deg
- ds: 40 # go another 40 um
exit_angle: -45 # finally, exit at -45 deg (until intersection with final port vector)
links:
mmi_short,o1: mmi_long,o1
ports:
o1: mmi_short,o2
o2: mmi_short,o3
aar_steps01.pic: uid fc093345, ports ['o1', 'o2'], references ['mmi_long', 'mmi_short', 'obstacle1', 'taper_1', 'extrude_1', 'taper_2', 'bend_euler_1', 'taper_3', 'extrude_2', 'taper_4', 'bend_euler_2', 'taper_5', 'extrude_3', 'taper_6', 'bend_euler_3', 'taper_7', 'extrude_4', 'taper_8', 'bend_euler_4', 'extrude_5'], 0 polygons
There are many different parameters you can put in the step directives. To make a complex route like this, a great way is to first sketch it out with the klayout ruler, then convert it to a set of ds and exit_angle step directives. Combine this with gf watch for live file-watching, and you can quickly iterate to achieve your desired route.
For example, consider the following circuit. Let’s start with the same two MMIs and obstacle as in the previous example.
[14]:
show_yaml_pic(sample_dir / "aar_steps02_initial.pic.yml")
2023-02-20 17:47:25.720 | INFO | gdsfactory.technology.layer_views:__init__:785 - Importing LayerViews from YAML file: /home/runner/work/gdsfactory/gdsfactory/gdsfactory/generic_tech/layer_views.yaml.
instances:
mmi_long:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 10
mmi_short:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 5
obstacle1:
component: rectangle
settings:
layer: M1
size: [30, 30]
placements:
mmi_short:
rotation: 180
mmi_long:
port: o1
rotation: 20
x: mmi_short,o1
y: mmi_short,o1
dx: 120
dy: 80
obstacle1:
port: ce
x: mmi_long,o1
y: mmi_long,o1
dx: -20
dy: -10
#routes:
# optical:
# routing_strategy: get_bundle_all_angle
# links:
# mmi_short,o1: mmi_long,o1
ports:
o1: mmi_short,o2
o2: mmi_short,o3
aar_steps02_initial.pic: uid b79ecf6f, ports ['o1', 'o2'], references ['mmi_long', 'mmi_short', 'obstacle1'], 0 polygons
First, trace the path you would like to take with the ruler, as shown below.
× Klayout tip: If you don’t already have angle displayed on your ruler in klayout, you can enable it by by going to File -> Setup, then from Rulers And Annotations / Templates, update the Label Format for your ruler to display both distance and angle.
Label Format: $D, angle=$(180/M_PI*atan2(Y,X)) deg
Then, translate the steps you took with the ruler into a set of steps directives.
[15]:
show_yaml_pic(sample_dir / "aar_steps02_final.pic.yml")
2023-02-20 17:47:26.483 | INFO | gdsfactory.technology.layer_views:__init__:785 - Importing LayerViews from YAML file: /home/runner/work/gdsfactory/gdsfactory/gdsfactory/generic_tech/layer_views.yaml.
/home/runner/work/gdsfactory/gdsfactory/gdsfactory/routing/all_angle.py:197: RouteWarning: Not enough room to route between ports: Port (name o2, center [131.29665856 80.47194512], width 0.5, orientation 20.0, layer (1, 0), port_type optical) and Port (name o1, center [130. 80.], width 0.5, orientation 200.0, layer (1, 0), port_type optical)
warnings.warn(message, RouteWarning)
instances:
mmi_long:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 10
mmi_short:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 5
obstacle1:
component: rectangle
settings:
layer: M1
size: [30, 30]
placements:
mmi_short:
rotation: 180
mmi_long:
port: o1
rotation: 20
x: mmi_short,o1
y: mmi_short,o1
dx: 120
dy: 80
obstacle1:
port: ce
x: mmi_long,o1
y: mmi_long,o1
dx: -20
dy: -10
routes:
optical:
routing_strategy: get_bundle_all_angle
settings:
steps:
- ds: 65
exit_angle: 90
- ds: 110
exit_angle: 0
- ds: 81.5
exit_angle: -65
- ds: 52
exit_angle: -158
- ds: 47.5
exit_angle: 112
links:
mmi_short,o1: mmi_long,o1
ports:
o1: mmi_short,o2
o2: mmi_short,o3
aar_steps02_final.pic: uid c90ec174, ports ['o1', 'o2'], references ['mmi_long', 'mmi_short', 'obstacle1', 'taper_1', 'extrude_1', 'taper_2', 'bend_euler_1', 'taper_3', 'extrude_2', 'taper_4', 'bend_euler_2', 'taper_5', 'extrude_3', 'taper_6', 'bend_euler_3', 'taper_7', 'extrude_4', 'taper_8', 'bend_euler_4', 'taper_9', 'extrude_5', 'taper_10', 'bend_euler_5', 'extrude_6', 'bend_euler_6', 'extrude_7'], 0 polygons
Perfect! Just like we sketched it!
You can also start to customize cross-sections and connectors of individual segments, as shown below.
[16]:
show_yaml_pic(sample_dir / "aar_steps03.pic.yml")
2023-02-20 17:47:27.416 | INFO | gdsfactory.technology.layer_views:__init__:785 - Importing LayerViews from YAML file: /home/runner/work/gdsfactory/gdsfactory/gdsfactory/generic_tech/layer_views.yaml.
instances:
mmi_long:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 10
mmi_short:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 5
obstacle1:
component: rectangle
settings:
layer: M1
size: [30, 30]
placements:
mmi_short:
rotation: 180
mmi_long:
port: o1
rotation: 20
x: mmi_short,o1
y: mmi_short,o1
dx: 120
dy: 80
obstacle1:
port: ce
x: mmi_long,o1
y: mmi_long,o1
dx: -20
dy: -10
routes:
optical:
routing_strategy: get_bundle_all_angle
settings:
steps:
- ds: 65
exit_angle: 90
cross_section: # set a custom cross-section here...
cross_section: strip
settings:
width: 2.0
- ds: 110
exit_angle: 0
- ds: 81.5
exit_angle: -65
connector: wonky # let's reuse our wonky connector again here
cross_section:
cross_section: strip
settings:
width: 4.0
- ds: 52
exit_angle: -158
- ds: 47.5
exit_angle: 112
links:
mmi_short,o1: mmi_long,o1
ports:
o1: mmi_short,o2
o2: mmi_short,o3
aar_steps03.pic: uid e42855da, ports ['o1', 'o2'], references ['mmi_long', 'mmi_short', 'obstacle1', 'taper_1', 'taper_2', 'extrude_1', 'bend_euler_1', 'taper_3', 'extrude_2', 'taper_4', 'bend_euler_2', 'taper_cross_section_1', 'taper_cross_section_2', 'text_1', 'bend_euler_3', 'taper_5', 'extrude_3', 'taper_6', 'bend_euler_4', 'taper_7', 'extrude_4', 'taper_8', 'bend_euler_5', 'extrude_5', 'bend_euler_6', 'extrude_6'], 0 polygons
Bundles#
You can also create all-angle bundles.
[17]:
show_yaml_pic(sample_dir / "aar_bundles01.pic.yml")
2023-02-20 17:47:28.238 | INFO | gdsfactory.technology.layer_views:__init__:785 - Importing LayerViews from YAML file: /home/runner/work/gdsfactory/gdsfactory/gdsfactory/generic_tech/layer_views.yaml.
/home/runner/work/gdsfactory/gdsfactory/gdsfactory/routing/all_angle.py:197: RouteWarning: Not enough room to route between ports: Port (name o2, center [124.586998 81.66953074], width 0.5, orientation 20.0, layer (1, 0), port_type optical) and Port (name o1, center [120. 80.], width 0.5, orientation 200.0, layer (1, 0), port_type optical)
warnings.warn(message, RouteWarning)
default_settings:
n_bundle:
value: 3
description: "The number of routes in the bundle"
instances:
{% for i in range(n_bundle) %}
wg_start_{{ i }}:
component: straight
wg_end_{{ i }}:
component: straight
{% endfor %}
obstacle1:
component: rectangle
settings:
layer: M1
size: [20, 20]
placements:
{% for i in range(n_bundle) %}
wg_end_{{ i }}:
rotation: 180
y: {{ i * 4 }}
wg_start_{{ i }}:
port: o1
rotation: {{ 20 - i * 5 }} # ports need not be aligned
x: wg_end_0,o1
y: wg_end_0,o1
dx: 120
dy: {{ 80 + i * 4 }}
{% endfor %}
obstacle1:
port: ce
x: wg_start_0,o1
y: wg_start_0,o1
dx: -25
dy: -10
routes:
optical:
routing_strategy: get_bundle_all_angle
settings:
separation: 3 # the separation between routes in the bundle. by default uses what is defined in the cross-section
steps:
- ds: 65
exit_angle: 90
- ds: 110
exit_angle: 0
- ds: 81.5
exit_angle: -65
- ds: 52
exit_angle: -158
- ds: 47.5
exit_angle: 140
links:
{% for i in [2, 1, 0] %}
wg_end_{{ i }},o1: wg_start_{{ i }},o1
{% endfor %}
ports:
o1: wg_start_0,o2
o2: wg_end_0,o2
aar_bundles01.pic: uid a83f7a5f, ports ['o1', 'o2'], references ['wg_start_0', 'wg_end_0', 'wg_start_1', 'wg_end_1', 'wg_start_2', 'wg_end_2', 'obstacle1', 'taper_1', 'extrude_1', 'taper_2', 'bend_euler_1', 'taper_3', 'extrude_2', 'taper_4', 'bend_euler_2', 'taper_5', 'extrude_3', 'taper_6', 'bend_euler_3', 'taper_7', 'extrude_4', 'taper_8', 'bend_euler_4', 'taper_9', 'extrude_5', 'taper_10', 'bend_euler_5', 'extrude_6', 'bend_euler_6', 'extrude_7', 'taper_11', 'extrude_8', 'taper_12', 'bend_euler_7', 'taper_13', 'extrude_9', 'taper_14', 'bend_euler_8', 'taper_15', 'extrude_10', 'taper_16', 'bend_euler_9', 'taper_17', 'extrude_11', 'taper_18', 'bend_euler_10', 'taper_19', 'extrude_12', 'taper_20', 'bend_euler_11', 'extrude_13', 'bend_euler_12', 'extrude_14', 'taper_21', 'extrude_15', 'taper_22', 'bend_euler_13', 'taper_23', 'extrude_16', 'taper_24', 'bend_euler_14', 'taper_25', 'extrude_17', 'taper_26', 'bend_euler_15', 'taper_27', 'extrude_18', 'taper_28', 'bend_euler_16', 'extrude_19', 'bend_euler_17', 'extrude_20', 'bend_euler_18', 'extrude_21'], 0 polygons
In addition to the parameters that can be customized for each step of a single route, bundles also let you customize the separation value step-by-step. For example, let’s space out the routes of that top, horizontal segment of the bundle.
[18]:
show_yaml_pic(sample_dir / "aar_bundles02.pic.yml")
2023-02-20 17:47:29.148 | INFO | gdsfactory.technology.layer_views:__init__:785 - Importing LayerViews from YAML file: /home/runner/work/gdsfactory/gdsfactory/gdsfactory/generic_tech/layer_views.yaml.
default_settings:
n_bundle:
value: 3
description: "The number of routes in the bundle"
instances:
{% for i in range(n_bundle) %}
wg_start_{{ i }}:
component: straight
wg_end_{{ i }}:
component: straight
{% endfor %}
obstacle1:
component: rectangle
settings:
layer: M1
size: [20, 20]
placements:
{% for i in range(n_bundle) %}
wg_end_{{ i }}:
rotation: 180
y: {{ i * 4 }}
wg_start_{{ i }}:
port: o1
rotation: {{ 20 - i * 5 }} # ports need not be aligned
x: wg_end_0,o1
y: wg_end_0,o1
dx: 120
dy: {{ 80 + i * 4 }}
{% endfor %}
obstacle1:
port: ce
x: wg_start_0,o1
y: wg_start_0,o1
dx: -25
dy: -10
routes:
optical:
routing_strategy: get_bundle_all_angle
settings:
separation: 3
steps:
- ds: 65
exit_angle: 90
- ds: 110
exit_angle: 0
- ds: 81.5
exit_angle: -65
separation: 10 # we can customize separation of individual segments of the bundle
- ds: 52
exit_angle: -158
- ds: 47.5
exit_angle: 140
links:
{% for i in range(n_bundle-1, -1, -1) %}
wg_end_{{ i }},o1: wg_start_{{ i }},o1
{% endfor %}
ports:
{% for i in range(n_bundle) %}
o{{ i }}: wg_start_{{ i }},o2
o{{ n_bundle + i }}: wg_end_{{ i }},o2
{% endfor %}
aar_bundles02.pic: uid 2eb3c3c7, ports ['o0', 'o3', 'o1', 'o4', 'o2', 'o5'], references ['wg_start_0', 'wg_end_0', 'wg_start_1', 'wg_end_1', 'wg_start_2', 'wg_end_2', 'obstacle1', 'taper_1', 'extrude_1', 'taper_2', 'bend_euler_1', 'taper_3', 'extrude_2', 'taper_4', 'bend_euler_2', 'taper_5', 'extrude_3', 'taper_6', 'bend_euler_3', 'taper_7', 'extrude_4', 'taper_8', 'bend_euler_4', 'taper_9', 'extrude_5', 'taper_10', 'bend_euler_5', 'extrude_6', 'bend_euler_6', 'extrude_7', 'taper_11', 'extrude_8', 'taper_12', 'bend_euler_7', 'taper_13', 'extrude_9', 'taper_14', 'bend_euler_8', 'taper_15', 'extrude_10', 'taper_16', 'bend_euler_9', 'taper_17', 'extrude_11', 'taper_18', 'bend_euler_10', 'taper_19', 'extrude_12', 'taper_20', 'bend_euler_11', 'extrude_13', 'bend_euler_12', 'extrude_14', 'taper_21', 'extrude_15', 'taper_22', 'bend_euler_13', 'taper_23', 'extrude_16', 'taper_24', 'bend_euler_14', 'taper_25', 'extrude_17', 'taper_26', 'bend_euler_15', 'extrude_18', 'bend_euler_16', 'extrude_19', 'bend_euler_17', 'extrude_20', 'bend_euler_18', 'extrude_21'], 0 polygons
[19]:
%%html
<style>
table {margin-left: 0 !important;}
</style>
Summary of available parameters#
We went through many examples above. Here is a quick recap of the parameters we used for the all-angle router.
Top-level settings#
These settings can be used in the bundle’s top-level settings block and will be applied to the whole bundle, unless overridden by an individual segment.
Name |
Function |
|---|---|
start_angle |
Defines the starting angle of the route (attaches a bend to the starting port to exit at that angle) |
end_angle |
Defines the angle leaving the end port of the route (attaches a bend to the end port to exit at that angle) |
bend |
The default component to use for the bends |
cross_section |
This cross-section will be passed to the bends and the straight connectors. However, these functions can use this information as they like (i.e. an auto-taper connector will attempt to taper to the cross-section but a low-loss connector may ignore it |
end_connector |
Specifies the connector to use for the final straight segment of the route |
end_cross_section |
Specifies the cross-section to use for the final straight segment of the route |
separation |
(bundle only) Specifies the separation between adjacent routes. If |
steps |
A set of directives for custom routing. This is expected to be a list of dictionaries with parameters per step as defined below |
Step directives#
These settings can be defined within individual steps to control the direction of each step.
Please note that an error will be thrown if a step is overconstrained. For example, x and y can be defined together in a single step only if exit_angle is not defined in the previous step. If exit_angle is defined (or angle is otherwise constrained by the port before it), you can only define one of x, y, ds, dx, or dy.
Name |
Function |
|---|---|
x |
Route to the given x coordinate (absolute) |
y |
Route to the given y coordinate (absolute) |
ds |
Proceed in the current orientation by this distance |
dx |
The x-component of distance traveled should be this value |
dy |
The y-component of distance traveled should be this value |
exit_angle |
After this segment, place a bend to exit with this angle (degrees) |
Step customizations#
These settings can also be set on individual steps to customize the route in that segment.
Name |
Function |
|---|---|
cross_section |
Use this cross-section for this segment. Will fall back to an auto-taper connector by default if this is specified alone, without |
connector |
Use this connector for this segment |
separation |
(bundles only) The separation to use between routes of this segment |
Python-based examples#
Most of the above examples were done in yaml syntax. Here are some additional examples creating the routes in pure python.
[20]:
import gdsfactory as gf
c = gf.Component("demo")
mmi = gf.components.mmi2x2(width_mmi=10, gap_mmi=3)
mmi1 = c << mmi
mmi2 = c << mmi
mmi2.move((100, 10))
mmi2.rotate(30)
routes = gf.routing.get_bundle_all_angle(
mmi1.get_ports_list(orientation=0),
[mmi2.ports["o2"], mmi2.ports["o1"]],
connector=None,
)
for route in routes:
c.add(route.references)
c
demo: uid ec40b08a, ports [], references ['mmi2x2_1', 'mmi2x2_2', 'bend_euler_1', 'bend_euler_2', 'extrude_1', 'bend_euler_3', 'bend_euler_4', 'extrude_2'], 0 polygons
[21]:
c = gf.Component("demo")
mmi = gf.components.mmi2x2(width_mmi=10, gap_mmi=3)
mmi1 = c << mmi
mmi2 = c << mmi
mmi2.move((100, 10))
mmi2.rotate(30)
routes = gf.routing.get_bundle_all_angle(
mmi1.get_ports_list(orientation=0),
[mmi2.ports["o2"], mmi2.ports["o1"]],
connector='low_loss',
)
for route in routes:
c.add(route.references)
c
demo: uid b7940e61, ports [], references ['mmi2x2_1', 'mmi2x2_2', 'bend_euler_1', 'bend_euler_2', 'taper_1', 'extrude_1', 'taper_2', 'bend_euler_3', 'bend_euler_4', 'taper_3', 'extrude_2', 'taper_4'], 0 polygons
[22]:
import gdsfactory as gf
from gdsfactory.routing.all_angle import get_bundle_all_angle
NUM_WIRES=10
@gf.cell
def inner_array():
c = gf.Component()
base = gf.components.straight(cross_section=gf.cross_section.strip).rotate(45)
for x in range(10):
for y in range(6):
base_ref = c.add_ref(base).move((x*20 - 90, y*20 - 50))
c.add_port(f"inner_{x}_{y}", port=base_ref.ports['o1'])
return c
@gf.cell
def outer_array():
c = gf.Component()
base = gf.components.straight(cross_section=gf.cross_section.strip)
for idx, theta in enumerate(range(0, 360, 6)):
base_ref = c.add_ref(base).move((300, 0)).rotate(theta)
c.add_port(f"outer_{idx}", port=base_ref.ports['o1'])
return c
@gf.cell
def chip():
c = gf.Component()
inner = c << inner_array()
outer = c << outer_array()
inner_ports = inner.get_ports_list()
outer_ports = outer.get_ports_list()
for n_route in range(NUM_WIRES):
routes = get_bundle_all_angle(
ports1=[inner_ports[n_route]],
ports2=[outer_ports[n_route]],
cross_section=gf.cross_section.strip,
bend=gf.components.bend_euler,
start_angle=-40,
steps=[{'ds': (NUM_WIRES - n_route) * 20},]
)
for route in routes:
c.add(route.references)
return c
gf.get_active_pdk().register_cross_sections(strip=gf.cross_section.strip)
c = chip()
c
/home/runner/work/gdsfactory/gdsfactory/gdsfactory/pdk.py:269: UserWarning: Overwriting cross_section 'strip'
warnings.warn(f"Overwriting cross_section {name!r}")
/home/runner/work/gdsfactory/gdsfactory/gdsfactory/routing/all_angle.py:197: RouteWarning: Not enough room to route between ports: Port (name o2, center [ 98.30363615 -71.15938834], width 1.0, orientation 320.0, layer (1, 0), port_type optical) and Port (name o2, center [ 98.30363615 -71.15938834], width 1.0, orientation 140.0, layer (1, 0), port_type optical)
warnings.warn(message, RouteWarning)
chip: uid 367f7d37, ports [], references ['inner_array_1', 'outer_array_1', 'bend_euler_1', 'taper_1', 'extrude_1', 'taper_2', 'bend_euler_2', 'bend_euler_3', 'taper_3', 'extrude_2', 'taper_4', 'bend_euler_4', 'taper_5', 'extrude_3', 'taper_6', 'bend_euler_5', 'bend_euler_6', 'taper_7', 'extrude_4', 'taper_8', 'bend_euler_7', 'taper_9', 'extrude_5', 'taper_10', 'bend_euler_8', 'bend_euler_9', 'taper_11', 'extrude_6', 'taper_12', 'bend_euler_10', 'taper_13', 'extrude_7', 'taper_14', 'bend_euler_11', 'bend_euler_12', 'taper_15', 'extrude_8', 'taper_16', 'bend_euler_13', 'taper_17', 'extrude_9', 'taper_18', 'bend_euler_14', 'bend_euler_15', 'taper_19', 'extrude_10', 'taper_20', 'bend_euler_16', 'taper_21', 'extrude_11', 'taper_22', 'bend_euler_17', 'bend_euler_18', 'taper_23', 'extrude_12', 'taper_24', 'bend_euler_19', 'taper_25', 'extrude_13', 'taper_26', 'bend_euler_20', 'bend_euler_21', 'taper_27', 'extrude_14', 'taper_28', 'bend_euler_22', 'taper_29', 'extrude_15', 'taper_30', 'bend_euler_23', 'bend_euler_24', 'taper_31', 'extrude_16', 'taper_32', 'bend_euler_25', 'taper_33', 'extrude_17', 'taper_34', 'bend_euler_26', 'bend_euler_27', 'taper_35', 'extrude_18', 'taper_36', 'bend_euler_28', 'taper_37', 'extrude_19', 'taper_38', 'bend_euler_29', 'bend_euler_30', 'taper_39', 'extrude_20', 'taper_40'], 0 polygons
[ ]: